package edu.hpu.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author 三分恶
 * @Date 2020/1/11
 * @Description
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 从 Spring5 开始，强制要求密码要加密，如果不想加密，可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder
     * BCryptPasswordEncoder 密码编码工具
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                //配置了两个用户,包括用户的用户名、角色、密码，用户密码已经加密（123）
                .withUser("zhangsan")
                //角色
                .roles("ADMIN")
                //密码已经加密，可以通过 passwordEncoder.encode("123")获取
                .password("$2a$10$dQLFreAJHM0F.4XAWQMTA.kB5W3H2.hjA6xBUJFHTFT7iHRzO0flm")
                //连接方法
                .and()
                .withUser("lisi").roles("USER").password("$2a$10$RDdoj3sm/RD7HzqSnU864eEE5kEZZxbyQqnYQJGrO2pgkUGCDutTC");
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()             //开启登录配置
                .antMatchers("/admin/**")
                //访问"admin/**"模式的URL必须具备ADMIN权限
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                //访问"user/**"模式的 URL必须具备 AD1IN、USER 的角色
                .access("hasAnyRole('ADMIN','USER') ")
                //除上面配置外的其它url都需要登录
                .anyRequest()
                .authenticated()
                .and()
                //开启表单登录
                .formLogin()
                //配置登录接口为“/login”
                .loginProcessingUrl("/login")
                //登录页面
                .loginPage("/login_page")
                //自定义认证所需的用户名和密码的参数名
                .usernameParameter("username")
                .passwordParameter("password")
                //定义登录成功的处理逻辑,这里是模拟前后端分离的情况，返回一段json，不前后端分离的话可以直接返回页面
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
                        Object principal = auth.getPrincipal();
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(200);
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 200);
                        map.put("msg", principal);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //定义登录失败的处理逻辑
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter out = response.getWriter();
                        response.setStatus(401);
                        Map<String, Object> map = new HashMap<>();
                        map.put("status", 401);
                        if (e instanceof LockedException) {
                            map.put("msg", "账号被锁定,登录失败 ！");
                        } else if (e instanceof BadCredentialsException) {
                            map.put("msg", "账户名或密码输入错误， 登录失败!");
                        } else if (e instanceof DisabledException) {
                            map.put("msg", "账户被禁用，登录失败！");
                        } else if (e instanceof AccountExpiredException) {
                            map.put("msg", "账户已过期，登录失败！");
                        } else {
                            map.put("msg", "登录失败！");
                        }
                        ObjectMapper om = new ObjectMapper ();
                        out .write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //登录相关的接口不需要认证
                .permitAll()
                .and()
                //注销登录
                .logout()
                //注销登录请求url
                .logoutUrl("/logout")
                //清除身份认证信息
                .clearAuthentication(true)
                //使 Session失效
                .invalidateHttpSession(true)
                //定义注销成功的业务逻辑，这里返回一段json
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        out.write("logout success");
                        out.flush();
                    }
                })
                .permitAll()
                .and()
                //关闭 csrf
                .csrf()
                .disable()
        ;
    }

}
